使用繼承雖然可以重複使用程式碼,但是繼承會有一些缺點,像是:
上面的兩個缺點,都顯示了一個問題,繼承容易有不預期的結果。以下面的範例來說,InstrumentedHashSe
t繼承了HashSet
,並用addCount
紀錄有多少item被加進去set,但因為HashSet
在執行addAll
的時候會呼叫add
,所以addCount
被重複計算,也許可以重寫addAll
或add
邏輯,甚至在加一個新的方法改善這個問題,但卻都不是好的解決方式。
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
// Broken - Inappropriate use of inheritance!
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedHashSet() {
}
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.add("a");
s.add("b");
s.addAll(Arrays.asList("c", "d", "e"));
System.out.println(s.getAddCount()); // 應該輸出 5, 但是結果是 8
System.out.println(s); // 應該輸出 [a, b, c, d, e]
}
}
因為改寫和加方法也有可能遇到一些問題:
由於上述的缺點,繼承比較適合在package裡面小規模使用,如果類別的使用範圍很廣,也許使用composition去實作需要擴充的功能,建立一個新的類別,並把原有類別的一個欄位指到新的類別,新類別就可以作為該模組的一個新功能,是一個比較好的選擇。
上面的範例原本是繼承HashSet
來擴充功能,但可以改成實作Set
這個interface,並宣告set
這個欄位指向HashSet
的物件,接著實作Set
的方法時,如果需要改變方法的行為,完全可以依照自己的邏輯實作,但如果不需要改變方法的行為,也可以直接回傳set
的執行結果,重複使用HashSet
的程式碼。
import java.util.*;
public class InstrumentedHashSet<E> implements Set<E> {
// The number of attempted element insertions
private int addCount = 0;
private final Set<E> set;
public InstrumentedHashSet() {
set = new HashSet<>();
}
public boolean add(E e) {
addCount++;
return set.add(e);
}
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return set.addAll(c);
}
public int getAddCount() {
return addCount;
}
@Override
public String toString() {
return set.toString();
}
public int size() {
return set.size();
}
public boolean isEmpty() {
return set.isEmpty();
}
public boolean contains(Object o) {
return set.contains(o);
}
public Iterator<E> iterator() {
return set.iterator();
}
public Object[] toArray() {
return set.toArray();
}
public <T> T[] toArray(T[] a) {
return set.toArray(a);
}
public boolean remove(Object o) {
return set.remove(o);
}
public boolean containsAll(Collection<?> c) {
return set.containsAll(c);
}
public boolean retainAll(Collection<?> c) {
return set.retainAll(c);
}
public boolean removeAll(Collection<?> c) {
return set.removeAll(c);
}
public void clear() {
set.clear();
}
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.add("a");
s.add("b");
s.addAll(Arrays.asList("c", "d", "e"));
System.out.println(s.getAddCount()); // 應該輸出 5
System.out.println(s); // 應該輸出 [a, b, c, d, e]
}
}
今天就介紹到這裡,明天會介紹使用composition改寫繼承更彈性的作法~